package com.acceptable.qucun.file.service.impl;

import com.acceptable.qucun.file.entity.CurrentDirectory;
import com.acceptable.qucun.file.entity.FileInfo;
import com.acceptable.qucun.file.entity.FileSource;
import com.acceptable.qucun.file.entity.TemplateDict;
import com.acceptable.qucun.file.entity.template.FileDeleteItem;
import com.acceptable.qucun.file.entity.template.FileShareItem;
import com.acceptable.qucun.file.entity.template.TemplateFile;
import com.acceptable.qucun.file.exception.FileOperationException;
import com.acceptable.qucun.file.mapper.*;
import com.acceptable.qucun.file.service.TemplateFileService;
import com.acceptable.qucun.generic.entity.FileItem;
import com.acceptable.qucun.generic.entity.RedisItem;
import com.acceptable.qucun.generic.exception.BaseException;
import com.acceptable.qucun.generic.util.*;
import com.acceptable.qucun.generic.util.result.ResultUtil;
import com.acceptable.qucun.generic.util.result.error.BaseErrorResult;
import com.acceptable.qucun.generic.util.result.error.FileErrorResult;
import com.acceptable.qucun.generic.util.result.success.FileSuccessResult;
import com.acceptable.qucun.generic.util.verify.FormatVerifyUtil;
import com.acceptable.qucun.user.entity.User;
import com.acceptable.qucun.user.mapper.UserMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 巴斯罗斯·K·赫斯洛利
 * @since 2021-08-29
 */
@Service
public class TemplateFileServiceImpl extends ServiceImpl<TemplateFileMapper, TemplateFile> implements TemplateFileService {
    // 最终部署在服务器上 部署时去掉
    @Autowired private FileItem fileItem;
    @Autowired protected TemplateFileMapper fileMapper;
    @Autowired private UserMapper userMapper;
    @Autowired private FileDeleteMapper deleteMapper;
    @Autowired private FileSourceMapper sourceMapper;
    @Autowired private FileDatabaseOperation fileDataOperation;
    @Autowired private FilePhysicalOperation filePhysicalOperation;
    @Autowired private FileShareMapper shareMapper;
    @Autowired private FileShareItemMapper shareItemMapper;
    @Autowired private RedisItem redisItem;

    /**
     *  是否需要保留数据库部分的重名检测由是否以物理文件名为file_name的值为判断依据
     *  @param destDictPath 目标目录
     *  @param fileName 欲复制/移动的文件名
     *  @param destDictId 目标目录id
     */
    private String getNewFileName(@NonNull String destDictPath, @NonNull String fileName,
                                  @NonNull Integer userId, @NonNull Long destDictId,
                                  @NonNull Date date){
        String physicalName = filePhysicalOperation.getNewFileName(destDictPath, fileName, date);
//        String databaseName = fileDataOperation.getNewFileName(userId, destDictId, fileName, date);
//        if (physicalName.equals(databaseName)){
            fileName = physicalName;
//        }
        return fileName;
    }

    // 持久层数据 所有的父文件夹数据
    private List<TemplateFile> selectParentDictList(Integer userId,
                                                    Long fileId){
        List<TemplateFile> parentDictList = new ArrayList<>();
        TemplateFile file = fileMapper.selectCurrentParentDict(userId, fileId);
        while (file != null && file.getFileId() != 0){
            parentDictList.add(file);
            file = fileMapper.selectCurrentParentDict(userId, file.getFileId());
        }
        Collections.reverse(parentDictList);
        return parentDictList;
    }
    /**
     *  文件移动或复制 通用版
     */
    private String copyOrMoveFile(Map<String, String> dataMap, boolean isCopy){
        Integer destUserId = Integer.parseInt(dataMap.get("dest_user_id"));
        Integer srcUserId = Integer.parseInt(dataMap.get("src_user_id"));
        List<Long> fileIdList = CodeUtil.string2List(dataMap.get("file_id"), Long.class);
        Long destDictId = Long.parseLong(dataMap.get("dest_dict_id"));
        String destDictPath = fileDataOperation.getAbsolutePath(destUserId, destDictId);

        Date date = new Date();
        // 记录已经操作的srcFile/destFile 用于恢复
//        List<File> operatedFile = new ArrayList<>();
        List<File> srcFileList = new ArrayList<>();
        List<File> destFileList = new ArrayList<>();
        List<TemplateFile> operationFileList = new ArrayList<>();

        for (Long fileId : fileIdList) {
            TemplateFile file = fileMapper.selectByFileId(srcUserId, fileId);
            String newName = this.getNewFileName(destDictPath, file.getFileName(), destUserId, destDictId, date);
            if (!file.getFileName().equals(newName)){
                file.setFileName(newName);
            }

            // 数据库插入或者修改需要的数据
            if (isCopy && file.getDict()) {
                TemplateDict dict = new TemplateDict(file);
                dict.setSubFileList(fileDataOperation.selectSubFileTree(srcUserId, file.getFileId()));
                operationFileList.add(dict);
            } else
                operationFileList.add(file);

            // 复制原文件(夹)
            String srcPath = fileDataOperation.getAbsolutePath(srcUserId, fileId);
            File srcFile = new File(srcPath);
            File destFile = new File(destDictPath + "/" + srcFile.getName());
            srcFileList.add(srcFile);
            destFileList.add(destFile);
        }

        // 进程占用导致物理移动失败
        try {
            boolean isShare = destUserId.equals(srcUserId);
            fileDataOperation.copyOrMoveFile(operationFileList, destUserId, destDictId,
                                             date, 0, isCopy);
            filePhysicalOperation.copyOrMoveFile(srcFileList, destFileList, date, isCopy);
            return isCopy ? FileSuccessResult.COPY_SUCCESS.getCode() :
                    FileSuccessResult.MOVE_SUCCESS.getCode();
        } catch (ReflectiveOperationException e){
            e.printStackTrace();
            // 复制: 失败删除目标文件
            throw new FileOperationException(FileErrorResult.COPY_ERROR);
        }
    }

    /**
     *  检查剩余空间
     */
    @Override
    public void checkRestVolume(Map<String, String> dataMap) {
        Integer userId = Integer.valueOf(dataMap.get("user_id"));

        // 剩余空间与总文件大小比较的检测 只需一次比较
        String rootDictPath = fileItem.getRootDictPrefix() + "/" + userMapper.selectByUserId(userId).getUserId();
        File rootDict = new File(rootDictPath);
        if(!rootDict.isDirectory())
            throw new FileOperationException(FileErrorResult.UPLOAD_ERROR);

        User user = userMapper.selectByUserId(userId);
        long freeVolume = user.getTotalVolume() - FileUtils.sizeOf(rootDict);
        long fileSize = Long.parseLong(dataMap.get("file_size"));
        if (freeVolume < fileSize){
            throw new FileOperationException(FileErrorResult.REST_VOLUME_ERROR);
        }

    }

    /**
     *  文件是否已经被自己/其他用户上传
     *  如果未被上传过 那么该文件是否存在部分数据
     *  如果被上传过 复制一份
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                   rollbackFor = {Exception.class})
    public Map<String, Object> uploadExistCheck(Map<String, String> dataMap,
                                                HttpServletRequest request){
        Long sendFileId = Long.parseLong(dataMap.get("file_id"));
        Integer sendUserId = Integer.parseInt(dataMap.get("user_id"));
        Map<String, Object> resMap = new HashMap<>();
        String fileMd5 = dataMap.get("file_md5");

        // 默认是 未被上传过
        // 存在上传的部分文件块 -- 未被上传
        FileSource source = null;
        boolean find = false;
        String tmpDictPath = "";
        TemplateFile file = null;
        File srcFile = null;

        {
            /*
                查找源文件位置且要求物理存在
                查找缓存文件块目录
                    可删除
                    前提: 保证文件源在数据库以及物理上均存在
            */
            for (FileSource source1: sourceMapper.selectListByFileMd5(fileMd5)){
                file = fileMapper.selectUndeletedById(source1.getUserId(), source1.getFileId());

                // 数据库中找到文件源 并且 物理存在
                String path = null;
                if (!find && file != null){
                    path = fileDataOperation.getAbsolutePath(source1.getUserId(), file.getFileId());
                    if (new File(path).exists()){
                        source = source1;
                        srcFile = new File(path);
                        find = true;
                    }
                }

                // 找缓存文件块目录
                if (StringFormatUtil.isEmpty(path)) {
                    path = fileDataOperation.getAbsolutePath(source1.getUserId(), source1.getFileId());
                }
                path = path.substring(0, path.lastIndexOf("/") + 1) + fileMd5;
                if (new File(path).exists()){
                    tmpDictPath = path;
                    break;
                }
            }

            // 已经上传过的文件块的redis数据
            Map<String, Object> myUploadedChunkMap = (Map<String, Object>) CodeUtil.string2Object(dataMap.get("redis_map"));

            Number destDictId = null;
            try{
                if (myUploadedChunkMap != null)
                    destDictId = (Number) myUploadedChunkMap.get("dest_dict_id");

                // 上述方式未找到可用的文件源 以及 缓存文件目录
                if (destDictId != null && source == null &&
                    StringFormatUtil.isEmpty(tmpDictPath))
                    tmpDictPath = fileDataOperation.getAbsolutePath(sendUserId, destDictId.longValue()) + "/" + fileMd5;
            } catch (FileOperationException e){
                resMap.put("code", FileSuccessResult.PART_EXIST_SUCCESS.getCode());
                resMap.put("uploaded_index_set", Collections.emptyList());
                return resMap;
            }

            // 获取上传过的物理文件的缓存文件块的索引序列
            Set<Long> uploadedIndexSet = new HashSet<>();
            File tmpDict = new File(tmpDictPath);
            if(source == null && !StringFormatUtil.isEmpty(tmpDictPath) && tmpDict.exists()){
                List<Long> uploadedIndexList = new ArrayList<>();
                if (myUploadedChunkMap != null)
                    uploadedIndexList = (List<Long>) myUploadedChunkMap.get("uploaded_index_set");

                for (Number index: uploadedIndexList){
                    if (new File(tmpDictPath + "/" + fileMd5 + "_" + index).exists()){
                        uploadedIndexSet.add(index.longValue());
                    }
                }

                // 如果缓存文件块的索引set与redis的不一致 以物理的为主
                if (!uploadedIndexSet.containsAll(uploadedIndexList)){
                    String[] tmpFileNames = tmpDict.list();
                    if (!FormatVerifyUtil.arrayIsEmpty(tmpFileNames)){
                        for (String tmpFileName: tmpFileNames){
                            Long index = Long.parseLong(tmpFileName.substring(
                                    tmpFileName.lastIndexOf("_") + 1));
                            uploadedIndexSet.add(index);
                        }
                    }

                    // 将正确的存进redis
                    {
                        String redisKey = sendUserId + "_chunk_uploaded_" + fileMd5;
                        String[] keys = new String[]{"file_md5", "uploaded_index_set", "dest_dict_id", "usage"};
                        Object[] values = new Object[]{fileMd5, uploadedIndexSet,
                                                       destDictId == null ? null: destDictId.longValue(),
                                                       redisItem.getUsageChunkUploaded()};

                        resMap = fileDataOperation.untreatedData2Redis(redisKey, request, keys, values);
                        resMap.clear();
                    }
                }

                uploadedIndexList.clear();
                uploadedIndexList.addAll(uploadedIndexSet);
                resMap.put("uploaded_index_set", uploadedIndexList);
                resMap.put("code", FileSuccessResult.PART_EXIST_SUCCESS.getCode());
                return resMap;
            }
        }

        // 不存在被上传的文件块 -- 文件未被上传
        if(source == null){
            // 不存在被上传的文件块 -- 文件没被完整的上传
            resMap.put("uploaded_index_set", Collections.emptyList());
            resMap.put("code", FileSuccessResult.PART_EXIST_SUCCESS.getCode());
            return resMap;
        }

        // 不存在被上传的文件块 -- 文件被完整上传
        // 逻辑存在 进行复制
        Long destDictId = Long.parseLong(dataMap.get("dest_dict_id"));
        String destDictPath =  fileDataOperation.getAbsolutePath(sendUserId, destDictId);
        Date date = new Date();
        String newFileName = filePhysicalOperation.getNewFileName(destDictPath, dataMap.get("file_name"), date);

        if(file != null){
            // 插入文件信息数据
            file.setUserId(sendUserId);
            file.setFileName(newFileName);
            file.setFileId(sendFileId);
            file.setPid(destDictId);
            file.setDeleted(false);
            fileMapper.insert(file);

            // 物理复制
            String newFilePath = destDictPath + "/" + newFileName;
            File destFile = new File(newFilePath);

            // 复制文件
            try {
                if(srcFile.exists()){
                    FileUtils.copyFile(srcFile, destFile);
                    resMap.put("code", FileSuccessResult.EXIST_SUCCESS.getCode());
                } else{
                    resMap.put("code", FileSuccessResult.PART_EXIST_SUCCESS.getCode());
                    resMap.put("uploaded_index_set", Collections.emptySet());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else{
            resMap.put("code", FileSuccessResult.PART_EXIST_SUCCESS.getCode());
            resMap.put("uploaded_index_set", Collections.emptySet());
        }

        return resMap;
    }

    /**
     *  上传文件块
     */
    @Override
    public String chunkUpload(Map<String, String> dataMap,
                              byte[] chunkData,
                              HttpServletRequest request){
        Integer userId = Integer.valueOf(dataMap.get("user_id"));
        Long destDictId = Long.valueOf(dataMap.get("dest_dict_id"));
        String fileMd5 = dataMap.get("file_md5");
        String chunkIndex = dataMap.get("chunk_index");
        Map<String, Object> redisMap = (Map<String, Object>) CodeUtil.string2Object(dataMap.get("redis_map"));

        // 完整的路径
        String destDictPath = fileDataOperation.getAbsolutePath(userId, destDictId);
        destDictPath = filePhysicalOperation.replaceRootDictInPath(userId, fileItem.getChunkTempDictName(), destDictPath);

        String tmpDictPath = destDictPath + "/" + fileMd5;
        String chunkName = tmpDictPath + "/" + fileMd5 + "_" + chunkIndex;

        // 文件夹是否存在 不存在则创建
        File dict = new File(tmpDictPath);
        if (!dict.exists()){
            try {
                if (!dict.mkdirs())
                    throw new FileNotFoundException("缓存文件夹创建失败。");
            } catch (FileNotFoundException e) {
                throw new FileOperationException(FileErrorResult.DEST_DICT_NOT_EXIST_ERROR);
            }
        }

        List<Long> uploadedIndexList = new ArrayList<>();
        boolean chunkExist = false;
        if (!FormatVerifyUtil.mapIsEmpty(redisMap)){
            List<Long> tmpList = (List<Long>) redisMap.get("uploaded_index_set");
            if (!FormatVerifyUtil.collectionIsEmpty(tmpList))
                uploadedIndexList = tmpList;
        }

        Set<Long> uploadedIndexSet = new HashSet<>(uploadedIndexList);
        // 集合存储对象 比较hashCode 不能直接contains
        for (Object index: uploadedIndexSet){
            if(String.valueOf(index).equals(chunkIndex)){
                chunkExist = true;
                break;
            }
        }

        try {
            // 非异步写入
//            String prefix = chunkName.substring(chunkName.lastIndexOf("/") + 1);
            {
                // 缓存文件块存在 不再写入
                if(new File(chunkName).exists() && chunkExist){
                    return FileSuccessResult.CHUNK_UPLOAD_SUCCESS.getCode();
                }

                // 文件块不存在 写入
                FileOutputStream out = new FileOutputStream(chunkName);
                out.write(chunkData);
                out.close();
            }

            // 将该文件块信息交由redis存储
            {
                String key = userId + "_" + redisItem.getUsageChunkUploaded() + "_" + fileMd5;
                uploadedIndexSet.add(Long.parseLong(chunkIndex));
                String[] keys = new String[]{"file_md5", "uploaded_index_set", "dest_dict_id", "usage"};
                Object[] values = new Object[]{fileMd5, uploadedIndexSet, destDictId, redisItem.getUsageChunkUploaded()};
                fileDataOperation.untreatedRedisStore(key, keys, values);
            }

            return FileSuccessResult.CHUNK_UPLOAD_SUCCESS.getCode();
        } catch (Exception e) {
            e.printStackTrace();
            throw new FileOperationException(FileErrorResult.UPLOAD_ERROR);
        }
    }

    /**
     *  所有块上传后 合并文件 率先上传本file_md5值的文件 将所有者信息存数据库
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    @Override
    public Map<String, Object> mergeFile(Map<String, String> dataMap,
                                         HttpServletRequest request){
        Integer userId = Integer.valueOf(dataMap.get("user_id"));
        Long destDictId = Long.valueOf(dataMap.get("dest_dict_id"));
        String fileMd5 = dataMap.get("file_md5");

        // 获取目标文件夹路径
        String destDictPath = fileDataOperation.getAbsolutePath(userId, destDictId);

        // 目标文件夹是否存在
        long fileId = Long.parseLong(dataMap.get("file_id"));
        String tmpDictPath = filePhysicalOperation.replaceRootDictInPath(userId,
                                fileItem.getChunkTempDictName(), destDictPath) + "/" + fileMd5;
        Map<String, Object> resMap = new HashMap<>();
        // 缓存文件夹不存在
        if(!(new File(tmpDictPath)).exists()) {
            resMap.put("code", FileErrorResult.TMP_DICT_NOT_EXIST_ERROR.getCode());
            resMap.put("msg", FileErrorResult.TMP_DICT_NOT_EXIST_ERROR.getMsg());
            return resMap;
        }

        /*
            新文件创建失败 缓存文件夹不存在 缺失部分文件数据
         */
        Date date = new Date();
        // 设置信息
        String fileName = filePhysicalOperation.getNewFileName(destDictPath, dataMap.get("file_name"), date);

        File newFile = new File(destDictPath + "/" + fileName);
        long lastModify = Long.parseLong(dataMap.get("last_modify_time"));
        {
            // 创建新文件
            try {
                if(!newFile.createNewFile()){
//                    System.out.println("新文件创建失败: " + newFile.getName());
                    throw new FileOperationException(FileErrorResult.CREATE_FILE_ERROR);
                }
                List<Boolean> flagList = new ArrayList<>();
                flagList.add(newFile.setLastModified(lastModify));
                flagList.add(newFile.setReadable(true, false));
                flagList.add(newFile.setWritable(true, false));
                if(flagList.contains(false)){
                    newFile.delete();
                    throw new FileOperationException(FileErrorResult.SET_INFO_ERROR);
                }
            } catch (IOException e) {
                e.printStackTrace();
                throw new FileOperationException(FileErrorResult.CREATE_FILE_ERROR);
            }
        }

        try {
            // 将实际已经上传的文件块合并
            Number chunkCount = Long.parseLong(dataMap.get("chunk_count"));

            List<File> chunkFileList = filePhysicalOperation.chunkFileList(tmpDictPath, fileMd5, chunkCount.intValue());
            long uploadedCount = chunkFileList.size();
            // 没有经过redis的文件块数检验
            // 完整上传
            if(chunkCount.longValue() == uploadedCount) {
                // 合并文件块
                {
                    FileOutputStream out = new FileOutputStream(newFile);
                    for (int i = 0; i < chunkCount.longValue(); i++) {
                        FileInputStream in = new FileInputStream(chunkFileList.get(i));
                        byte[] buffer = new byte[in.available()];
                        in.read(buffer);
                        out.write(buffer);
                        out.flush();
                        in.close();
                    }
                    out.close();
                }

                String mime = FileUtil.getMimeByFilePath(newFile.getPath());

                // 插入文件信息至个人文件表
                {
                    TemplateFile file = new TemplateFile();
                    file.setUserId(userId);
                    file.setFileId(new RandomUtil().fileIdUnique(13, userId, fileMapper));
                    file.setFileSize(FileUtils.sizeOf(newFile));
                    file.setFileName(fileName);
                    file.setFileMime(mime);
                    file.setDeleted(false);
                    file.setDict(false);
                    file.setPid(destDictId);
                    file.setFileMd5(fileMd5);
                    file.setCreateTime(TimeUtil.date2LocalDateTime(new Date()));
                    file.setLastModifyTime(TimeUtil.date2LocalDateTime(new Date(lastModify)));
                    fileMapper.insert(file);
                }

                // 添加整个文件的相关数据至文件源表
                {
                    FileSource source = sourceMapper.selectById(fileMd5);
                    boolean exist = false;
                    if(source == null)
                        source = new FileSource();
                    else
                        exist = true;
                    source.setFileId(fileId);
                    source.setFileMd5(fileMd5);
                    source.setUserId(userId);

                    if(exist)
                        sourceMapper.updateById(source);
                    else
                        sourceMapper.insert(source);
                }

                // 删除缓存文件所在文件夹
                FileUtils.deleteDirectory(new File(tmpDictPath));

                // redis相关
                {
                    // 删除redis中的该文件块数据 可能删除不掉 直接替换
                    /*
                        redisTemplate.expire(key, Duration.ofSeconds(-1));
                        redisTemplate.delete(key);
                     */
                    String key = userId + "_" + redisItem.getUsageChunkUploaded() +"_" + fileMd5;
                    String[] keys = new String[]{"file_md5", "uploaded_index_set", "dest_dict_id", "usage"};
                    Object[] values = new Object[]{"", Collections.emptyList(), 0, ""};
                    resMap = fileDataOperation.untreatedData2Redis(key, request, keys, values);
                    resMap.clear();
                }

                resMap.put("code", FileSuccessResult.MERGE_SUCCESS.getCode());
            } else{
                throw new FileOperationException(FileErrorResult.CHUNK_LACK_ERROR);
            }
            return resMap;
        } catch (Exception e) {
            newFile.delete();
            e.printStackTrace();
            if (ResultUtil.getErrorResultFromAll(e.getMessage()) != null){
                throw new BaseException(ResultUtil.getErrorResultFromAll(e.getMessage()));
            }
            throw new FileOperationException(FileErrorResult.CREATE_FILE_ERROR);
        }
    }

    /**
     *  查询当前所在目录的子文件信息
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    readOnly = true)
    public List<FileInfo> selectExistedSingleDictSubFile(@NonNull Integer userId,
                                                         @NonNull Long parentDictId,
                                                         @NonNull Boolean shareStatus) {
        // 查询的子文件信息
        List<TemplateFile> childFileList = fileMapper.selectSubFile(userId, parentDictId);

        List<FileInfo> childFileInfoList = new ArrayList<>();

        // 子文件的父目录信息
        if(childFileList.size() == 0){
            return childFileInfoList;
        }
        List<FileInfo> pathDictList = fileDataOperation.getPathDictList(userId,
                childFileList.get(0).getFileId(),
                shareStatus);
        for (TemplateFile childFile: childFileList){
            FileInfo childFileInfo = new FileInfo();
            if (childFile.getDeleted())
                continue;
            // 设置信息
            {
                childFileInfo.setFileId(childFile.getFileId());
                childFileInfo.setFileName(childFile.getFileName());
                childFileInfo.setFileMime(childFile.getFileMime());
                long fileSize = childFile.getFileSize();
                if(childFile.getDict()){
                    fileSize = this.selectUsedVolume(userId, childFile.getFileId());
                }

                childFileInfo.setFileSize(fileSize);

                String lastModifyTime = childFile.getLastModifyTime().toString();

                childFileInfo.setLastModifyTime(lastModifyTime);
                childFileInfo.setIsDict(childFile.getDict());
                childFileInfo.setPathDictList(pathDictList);
                childFileInfo.setIsDeleted(childFile.getDeleted());
                String createTime = childFile.getCreateTime().toString();
                childFileInfo.setCreateTime(createTime);
            }
            childFileInfoList.add(childFileInfo);
        }
        return childFileInfoList;
    }
    /**
     *  根目录下的子文件信息
     * @param shareStatus 是否是在分享状态下查看被分享文件
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
    public CurrentDirectory selectExistedSingleDictInfo(@NonNull Integer userId,
                                                        @NonNull Long fileId,
                                                        @NonNull Boolean shareStatus){
        CurrentDirectory dict = new CurrentDirectory();
        if(fileId.equals(fileItem.getUndeletedFileDictId()))
            dict.setIsRootDict(true);
        else{
            dict.setIsRootDict(false);
        }

        // 不能是上传临时目录id, 回收站的id, 被删除的目录的id
        if (fileId.equals(fileItem.getDustbinDictId()) ||
            fileId.equals(fileItem.getChunkTempDictId()) ||
            fileMapper.selectUndeletedById(userId, fileId) == null){
            fileId = fileItem.getUndeletedFileDictId();
        }
        dict.setCurrentDictId(fileId);
        dict.setSubFileList(this.selectExistedSingleDictSubFile(userId, fileId, shareStatus));

        List<FileInfo> pathDictList = fileDataOperation.getPathDictList(userId, fileId, shareStatus);
        String currentDictName = fileMapper.selectByFileId(userId, fileId).getFileName();
        FileInfo current = new FileInfo();
        current.setFileId(fileId);
        current.setFileName(currentDictName);
        pathDictList.add(current);
        dict.setPath(pathDictList);
        return dict;
    }

    /**
     *  查询已经使用的容量(不含已经删除的文件)
     */
    @Override
    public long selectUsedVolume(Integer userId,
                                 Long parentDictId) {
        List<TemplateFile> childFileList = fileMapper.selectSubFile(userId, parentDictId);
        if (childFileList == null || childFileList.size() == 0){
            return 0L;
        }

        long usedVolume = 0L;
        for (TemplateFile file: childFileList){
            if(file.getDeleted())
                continue;
            if(!file.getDict()){
                usedVolume += file.getFileSize();
            } else{
                usedVolume += this.selectUsedVolume(userId, file.getFileId());
            }
        }
        return usedVolume;
    }

    /**
     *  查询指定id的文件信息
     */
    @Override
    public FileInfo selectExistedSingleFileInfo(@NonNull Integer userId,
                                                @NonNull Long fileId,
                                                @NonNull Boolean shareSatus) {
        TemplateFile file = fileMapper.selectByFileId(userId, fileId);

        // 设置查询的文件信息
        FileInfo fileInfo;
        {
            fileInfo = new FileInfo();
            fileInfo.setFileId(file.getFileId());
//            System.out.println(StringFormatUtil.clearDate(file.getFileName()));
            fileInfo.setFileName(file.getFileName());
            fileInfo.setFileSize(file.getFileSize());
            fileInfo.setFileMime(file.getFileMime());

            if(file.getDict()){
                fileInfo.setSubFileList(this.selectExistedSingleDictSubFile(userId, fileId, shareSatus));
            }

            List<FileInfo> pathDictList = fileDataOperation.getPathDictList(userId, fileId, shareSatus);
            fileInfo.setPathDictList(pathDictList);

            fileInfo.setIsDict(file.getDict());

            fileInfo.setCreateTime(file.getCreateTime().toString());
            fileInfo.setLastModifyTime(file.getLastModifyTime().toString());
        }

        return fileInfo;
    }

    /**
     *  查询指定id的目录信息 (子文件仅包含目录)
     */
    @Override
    public Map<String, Object> selectExistedSingleDictSubDict(Integer userId, Long fileId) {
        List<TemplateFile> subDictList = fileMapper.selectSubDict(userId, fileId);
        Map<String, Object> resMap = new HashMap<>();
        List<CurrentDirectory> directoryList = new ArrayList<>();
        for (TemplateFile dict: subDictList){
            if (dict.getDeleted() ||!dict.getDict())
                continue;

            CurrentDirectory dictInfo = new CurrentDirectory();
            dictInfo.setCurrentDictId(dict.getFileId());
            dictInfo.setDictName(dict.getFileName());
            dictInfo.setIsRootDict(false);
            Integer count = fileMapper.countSubUndeletedDict(userId, dict.getFileId());
            dictInfo.setIsLeaf(count == 0);
            directoryList.add(dictInfo);
        }

        // 设置查询的文件信息
        resMap.put("code", FileSuccessResult.SUB_DICT_INFO_SUCCESS.getCode());
        resMap.put("sub_dict_list", directoryList);
        return resMap;
    }

    /**
     *  创建文件夹
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    rollbackFor = {Exception.class})
    public Map<String, Object> createDict(Map<String, String> dataMap) {
        Integer userId = Integer.parseInt(dataMap.get("user_id"));
        Long currentDictId = Long.parseLong(dataMap.get("current_dict_id"));
        Boolean shareStatus = Boolean.parseBoolean(dataMap.get("share_status"));

        String parentDictPath = fileDataOperation.getAbsolutePath(userId, currentDictId);
        Map<String, Object> resMap = new HashMap<>();
        if (!new File(parentDictPath).exists()){
            resMap.put("code", FileErrorResult.DEST_DICT_NOT_EXIST_ERROR.getCode());
            resMap.put("msg", FileErrorResult.DEST_DICT_NOT_EXIST_ERROR.getMsg());
            return resMap;
        }

        String dictName = dataMap.get("new_name");
        TemplateFile newDict = new TemplateFile();
        Long newId = Long.parseLong(dataMap.get("file_id"));
        {
            newDict.setUserId(userId);
            newDict.setDict(true);
            newDict.setFileId(newId);
            newDict.setFileName(dictName);
            newDict.setPid(currentDictId);
        }

        try{
            fileMapper.insert(newDict);

            Date date = new Date();
            String newFileName = filePhysicalOperation.getNewFileName(parentDictPath, dictName, date);
            newDict.setFileName(newFileName);
            boolean success = new File(parentDictPath + "/" + newFileName).mkdir();
            if(success){
                FileInfo newDictInfo = this.selectExistedSingleFileInfo(userId, newId, shareStatus);
                resMap.put("code", FileSuccessResult.CREATE_DICT_SUCCESS.getCode());
                resMap.put("file_info", newDictInfo);
            } else{
                throw new FileOperationException(FileErrorResult.CREATE_FILE_ERROR);
            }

        } catch (FileOperationException e){
            BaseErrorResult error = ResultUtil.getErrorResultFromAll(e.getMessage());
            resMap.put("code", error.getCode());
            resMap.put("msg", error.getMsg());
        }
        return resMap;
    }

    /**
     *  重命名
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    rollbackFor = {Exception.class})
    public Map<String, Object> renameExistedSingleFile(Map<String, String> dataMap){
        Integer userId = Integer.parseInt(dataMap.get("user_id"));
        Long fileId = Long.parseLong(dataMap.get("file_id"));
        String newName = dataMap.get("new_name");
        Map<String, Object> resMap = new HashMap<>();
        Date date = new Date();

        // 获取路径 以及 新名称
        String filePath = fileDataOperation.getAbsolutePath(userId, fileId);
        String dictPath = filePath.substring(0, filePath.lastIndexOf("/") + 1);
        newName = filePhysicalOperation.getNewFileName(dictPath, newName, date);

        // 逻辑修改更新数据库的名称
        int rowNum = 0;
        {
            TemplateFile file = fileMapper.selectByFileId(userId, fileId);
            file.setUserId(userId);
            file.setFileName(newName);
            file.setLastModifyTime(TimeUtil.date2LocalDateTime(date));
            rowNum = fileMapper.updateByFileId(file);
        }

        // 物理修改名称
        String newFilePath = dictPath + newName;
        boolean success = new File(filePath).renameTo(new File(newFilePath));
        if(success &&  rowNum == 1){
            resMap.put("code", FileSuccessResult.RENAME_SUCCESS.getCode());
            FileInfo fileInfo = this.selectExistedSingleFileInfo(userId, fileId, false);
            resMap.put("file_info", fileInfo);
            return resMap;
        } else{
            throw new FileOperationException(FileErrorResult.RENAME_ERROR);
        }
}

    /**
     *  逻辑删除多数文件
     *      1. 向userId_delete中添加删除项
     *      2. 批量修改userId_file中的文件项is_delete=TRUE
     *      3. 同名检测并移动至"回收站"文件夹
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                   rollbackFor = {Exception.class})
    public String logicalDeleteExistedMultiFile(Map<String, String> dataMap){
        Integer userId = Integer.parseInt(dataMap.get("user_id"));
        List<Long> fileIdList =  CodeUtil.string2List(dataMap.get("file_id"), Long.class);
//        List<Long> subFileIdList = new LinkedList<>();
//        for (Long fileId: fileIdList){
//            for (TemplateFile file: fileDataOperation.selectSubFileList(userId, fileId)){
//                subFileIdList.add(file.getFileId());
//            }
//        }
//        fileIdList.addAll(subFileIdList);

        List<File> destFileList = new ArrayList<>();
        List<File> srcFileList = new ArrayList<>();
        Date date = new Date();
        LocalDateTime nowTime = TimeUtil.date2LocalDateTime(date);

        for (Long fileId: fileIdList){
            // 用于将被删除文件移动到回收站的文件夹中
            String path = fileDataOperation.getAbsolutePath(userId, fileId);
            File srcFile = new File(path);
            srcFileList.add(srcFile);
            path = fileDataOperation.getAbsolutePath(userId, fileItem.getDustbinDictId()) + "/" + srcFile.getName();
            File destFile = filePhysicalOperation.getNewFileName(new File(path), date);
            destFileList.add(destFile);

            // 添加删除项到表中
            FileDeleteItem item = new FileDeleteItem();
            item.setFileId(fileId);
            item.setDeleteTime(nowTime);
            item.setSrcPid(fileMapper.selectByFileId(userId, fileId).getPid());
            deleteMapper.insert(userId, item);
        }

        try {
            // 逻辑删除
            int deletedColumn = fileMapper.logicalBatchDelete(userId, fileItem.getDustbinDictId(), fileIdList);
            if (FormatVerifyUtil.collectionIsEmpty(fileIdList) || fileIdList.size() != deletedColumn){
                throw new FileOperationException(FileErrorResult.LOGICAL_DELETE_ERROR);
            }

            // 物理移动至"回收站"文件夹
            filePhysicalOperation.copyOrMoveFile(srcFileList, destFileList, date, false);
            return FileSuccessResult.LOGICAL_DELETE_SUCCESS.getCode();
        } catch (Exception e) {
            e.printStackTrace();

            throw new FileOperationException(FileErrorResult.LOGICAL_DELETE_ERROR);
        }
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    rollbackFor = {Exception.class})
    public String moveExistedMultiFile(Map<String, String> dataMap) {
        return this.copyOrMoveFile(dataMap, false);
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    rollbackFor = {Exception.class})
    public String copyExistedMultiFile(Map<String, String> dataMap){
        return this.copyOrMoveFile(dataMap, true);
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public Map<String, Object> downloadExistedMultiCheck(Map<String, String> dataMap) {
        Integer userId = Integer.valueOf(dataMap.get("user_id"));
        List<Long> fileIdList = CodeUtil.string2List(dataMap.get("file_id"), Long.class);

        List<Long> existedId = new ArrayList<>();
        List<String> existedPath = new ArrayList<>();
        List<Long> notExistedId = new ArrayList<>();
        for (Long fileId: fileIdList){
            String filePath = fileDataOperation.getAbsolutePath(userId, fileId);
            if (new File(filePath).exists()){
                existedId.add(fileId);
                existedPath.add(filePath);
            } else{
                notExistedId.add(fileId);
            }
        }

        // 直接删除用户能够看到 但 物理上不存在的文件
        if (notExistedId.size() > 0)
            fileMapper.deleteBatchByFileId(userId, notExistedId);
//        fileMapper.logicalBatchDeleteFile(userId, notExistedId);
        String code = FileSuccessResult.ALL_EXIST_SUCCESS.getCode();
        if (fileIdList.size() > existedPath.size()){
            code = FileSuccessResult.NOT_ALL_EXIST_SUCCESS.getCode();
        }

        Map<String, Object> resMap = new HashMap<>();
        resMap.put("code", code);
        resMap.put("existed_id", existedId);
        resMap.put("existed_path", existedPath);
        return resMap;
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
    public ResponseEntity<byte[]> fileEntireDownload(Map<String, String> dataMap) {
        String filePath = dataMap.get("file_path");
        Integer userId = Integer.valueOf(dataMap.get("user_id"));
        Long file_id = Long.valueOf(dataMap.get("file_id"));

        File file = new File(filePath);
        if (!file.exists()){
            filePath = this.fileDataOperation.getAbsolutePath(userId, file_id);
            file = new File(filePath);
            if (!file.exists()){
                throw new FileOperationException(FileErrorResult.NOT_EXIST_ERROR);
            }
        }

        ResponseEntity<byte[]> entity;
        try {
            // 默认是普通文件
            File destFile = file;
            if (file.isDirectory()){
                destFile = filePhysicalOperation.compressDirectory(userId, file);
            }
            // 普通文件直接读取数据
            byte[] arr = filePhysicalOperation.getFileBinaryData(destFile);
            String mime = FileUtil.getMimeByFilePath(destFile.getAbsolutePath());

            if (file.isDirectory()){
                // 目录压缩且读取流对象后 删除压缩文件
                filePhysicalOperation.deleteFile(Collections.singletonList(destFile));
            }
            // 响应头的文件名
            HttpHeaders headers = new HttpHeaders();
            headers.set("Content-Disposition",
                        "attachment; filename=\"" + filePath.substring(filePath.lastIndexOf("/")+1) + "\"");
            headers.set("Content-Type", mime);
            entity = new ResponseEntity<>(arr, headers, HttpStatus.OK);
            return entity;
        } catch (Exception e) {
            e.printStackTrace();
            throw new FileOperationException(FileErrorResult.DOWNLOAD_ERROR);
        }
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public Map<String, Object> filterCategory(Map<String, String> dataMap) {
        Integer userId = Integer.valueOf(dataMap.get("user_id"));
        String methodName = dataMap.get("method");
        String methodValue = dataMap.get(methodName);
        boolean deleted = "deleted".equals(methodName) && "deleted".equals(methodValue);
        List<TemplateFile> fileList;
        Date now = new Date();
        try {
            if (deleted){
                // 查询需要进行物理以及数据库删除的文件
                List<FileDeleteItem> deleteItemList = deleteMapper.selectAll(userId);
                List<Long> deletedIdList = new ArrayList<>();
                for (FileDeleteItem item: deleteItemList){
                    LocalDateTime endTime = item.getDeleteTime().plusDays(fileItem.getMaxRestDay());
                    if (now.after(TimeUtil.localDateTime2Date(endTime))){
                        deletedIdList.add(item.getFileId());
                    }
                }

                this.deleteFile(userId, deletedIdList);
                fileList = fileDataOperation.filterFileByDeleted(userId);

            } else{
                // 根据文件类型分类
                Method method = fileDataOperation.getClass().getDeclaredMethod("filterFileBy" + StringUtils.capitalize(methodName),
                        Integer.class, String.class);
                fileList = (List<TemplateFile>) method.invoke(fileDataOperation, userId, methodValue);
            }
            // 除查询回收站内容外 其他均查询未被删除的文件信息
            // 回收站有删除时间 须额外查询
            if (!deleted){
                fileList = fileDataOperation.selectUndeletedFile(fileList);
            }

            List<FileInfo> fileInfoList = fileDataOperation.templateFile2FileInfo(fileList);
            for(FileInfo fileInfo: fileInfoList){
                List<FileInfo> pathDictList = fileDataOperation.getPathDictList(userId, fileInfo.getFileId(), false);
                fileInfo.setPathDictList(pathDictList);
            }

            // 被删除文件的额外信息设置
            if (deleted){
                for (FileInfo info: fileInfoList){
                    FileDeleteItem item = deleteMapper.selectByFileId(userId, info.getFileId());
                    LocalDateTime endTime = item.getDeleteTime().plusDays(fileItem.getMaxRestDay());
                    info.setDeleteTime(item.getDeleteTime().toString());
                    info.setExpireTime(endTime.toString());
                }
            }

            String code = FileSuccessResult.FILTER_CATEGORY_SUCCESS.getCode();
            String[] keys = new String[]{"file_list", "code"};
            Object[] values = new Object[]{fileInfoList, code};
            return ResultUtil.resultMap(keys, values);
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
            throw new FileOperationException(FileErrorResult.FILTER_ERROR);
        }
    }

    /**
     *  批量还原被逻辑删除的文件
     *      1. 修改userId_file中文件项的is_delete parent_dict_id
     *      2. 删除userId_delete中的删除项
     *      3. 同名检测 移动至原父目录下
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                    rollbackFor = {Exception.class})
    public String logicalRecovery(Map<String, String> dataMap) {
        Integer userId = Integer.parseInt(dataMap.get("user_id"));
        List<Long> fileIdList = CodeUtil.string2List(dataMap.get("file_id"), Long.class);
        List<File> srcFileList = new ArrayList<>();
        List<File> destFileList = new ArrayList<>();
        Date date = new Date();
        // 获取物理上操作需要的文件对象
        for (Long fileId: fileIdList){
            TemplateFile file = fileMapper.selectByFileId(userId, fileId);
            FileDeleteItem deleteItem = deleteMapper.selectByFileId(userId, fileId);
            /*
                目标位置可能有用户已经上传的同名文件
                因此需要做物理的同名检测 但可能会改变目标文件的绝对路径
                若在改变后 通过目标文件的绝对路径获取回收站文件的路径则会出错
                故此处先获取回收站文件的路径 后进行目标文件的重名检测
             */

            // 回收站文件
            String srcDictPath = fileDataOperation.getAbsolutePath(userId, file.getPid());
            String srcPath = srcDictPath + "/" + file.getFileName();
            srcFileList.add(new File(srcPath));

            // 还原之后的文件
            String destDictPath = fileDataOperation.getAbsolutePath(userId, deleteItem.getSrcPid());
//            String destDictPath = fileDataOperation.getAbsolutePath(userId, fileItem.getUndeletedFileDictId());
            String destPath = destDictPath + "/" +
                        filePhysicalOperation.getNewFileName(destDictPath, file.getFileName(), date);
            File destFile = new File(destPath);
            destFileList.add(destFile);
        }

        try {
            boolean logical = fileDataOperation.batchLogicalRecovery(userId, fileIdList, date);
            Map<String, List<File>> operatedFile = filePhysicalOperation.copyOrMoveFile(srcFileList, destFileList, date, false);
            List<File> srcFile = operatedFile.get("operatedSrcFile");
            boolean physical = srcFile.size() == operatedFile.get("operatedDestFile").size() &&
                               srcFile.size() == fileIdList.size();
            if (logical && physical){
                return FileSuccessResult.LOGICAL_RECOVERY_SUCCESS.getCode();
            } else{
                throw new FileOperationException(FileErrorResult.LOGICAL_RECOVERY_ERROR);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new FileOperationException(FileErrorResult.LOGICAL_RECOVERY_ERROR);
        }
    }

    // 在物理和数据库方面删除文件
    private void deleteFile(@NonNull Integer userId,
                            @NonNull List<Long> fileIdList){
        if (FormatVerifyUtil.collectionIsEmpty(fileIdList))
            return;

        // 查询需要删除的分享项和文件列表
        List<TemplateFile> deletedDataList = fileMapper.selectBatchByFileId(userId, fileIdList);

        List<Integer> deleteShareIdList = new ArrayList<>();
        List<File> deletedFileList = new ArrayList<>();
        for (Long fileId: fileIdList){
            TemplateFile file = fileMapper.selectByFileId(userId, fileId);

            // 如果删除文件夹 还需额外递归删除其下的子文件
            if (file.getDict()){
                List<TemplateFile> subFileList = fileDataOperation.selectSubFileList(userId, fileId);
                deletedDataList.addAll(subFileList);
                System.out.println(file.getFileName() + "子文件有: " + subFileList);
            }

            /*
                由本项查得的shareId 必然包含本项
                还可能包含在此次删除操作中的其他文件项
             */
            List<FileShareItem> shareItemList = shareItemMapper.selectByFileId(userId, fileId);
            for (FileShareItem shareItem: shareItemList){
                List<TemplateFile> shareFileList = new ArrayList<>();
                Integer shareId = -1;
                if (shareItem != null){
                    shareId = shareItem.getShareId();
                    shareFileList = fileMapper.selectByShareId(userId, shareId);
                    shareFileList.removeAll(deletedDataList);

                }
                /*
                    移除本次操作中所有的文件项
                    为空 则表明此分享项下没有文件可删除该分享项
                    反之 可能出现文件项处于回收站 此时文件可能被还原回去 故不做操作
                */
                if (shareFileList.size() == 0){
                    deleteShareIdList.add(shareId);
                }
            }

            String dictPath = fileDataOperation.getAbsolutePath(userId, fileItem.getDustbinDictId());
            String path = dictPath + "/" + file.getFileName();
            deletedFileList.add(new File(path));
        }

        /*
            不做删除数判断的原因:
                1. 文件获得方式未知 (文件获得方式未做而已)
                   若为分享得到则 file_source中可能无相关记录
                   来源项的数目可能与选中项不一致
                2. 选中项可能包含文件夹 此时 删除项、文件项的删除数与选中数不一致
                3. 部分分享项当存在未被选中或者未被删除的项时 删除数与选中数不一致
                4. 物理删除本身就可能因为占用问题出错 而且无法进行还原
         */

        // 将被删除文件中的子文件添加进删除队列
        for (TemplateFile file: deletedDataList){
            fileIdList.add(file.getFileId());
        }

        sourceMapper.deleteBatchByFileId(userId, fileIdList);
        deleteMapper.deleteBatchByFileId(userId, fileIdList);
        shareItemMapper.deleteBatchByFileId(userId, fileIdList);

        if (!FormatVerifyUtil.collectionIsEmpty(deleteShareIdList))
            shareMapper.deleteBatchIds(deleteShareIdList);

        fileMapper.deleteBatchByFileId(userId, fileIdList);
        filePhysicalOperation.deleteFile(deletedFileList);
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,
                  rollbackFor = {Exception.class})
    public String physicalDeleteByVerifyCode(Map<String, String> dataMap) {
        Integer userId = Integer.parseInt(dataMap.get("user_id"));
        List<Long> fileIdList = CodeUtil.string2List(dataMap.get("file_id"), Long.class);
        this.deleteFile(userId, fileIdList);
        return FileSuccessResult.PHYSICAL_DELETE_SUCCESS.getCode();
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public String physicalDeleteExpiredFile(Integer userId){
        LocalDateTime curTime = TimeUtil.date2LocalDateTime(new Date());
        List<FileDeleteItem> deleteItemList = deleteMapper.selectExpiredFile(userId, curTime);
        List<Long> idList = new LinkedList<>();
        for (FileDeleteItem item: deleteItemList){
            idList.add(item.getFileId());
        }

        try{
            if (deleteItemList.size() > 0){
                this.deleteFile(userId, idList);
            }
        } catch (Exception e){
            throw new FileOperationException(FileErrorResult.PHYSICAL_DELETE_ERROR);
        }
        return FileSuccessResult.PHYSICAL_DELETE_SUCCESS.getCode();
    }
}
